/* FCE Ultra - NES/Famicom Emulator
 *
 * Copyright notice for this file:
 *  Copyright (C) 2002 Xodnizel
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

static int RecalcCustom(void);

#define VF_DDSTRETCHED     1

#define VEF_LOSTSURFACE 1
#define VEF____INTERNAL 2

#define VMDF_DXBLT 1
#define VMDF_STRFS 2

typedef struct {
	int x;
	int y;
	int bpp;
	int flags;
	int xscale;
	int yscale;
	RECT srect;
	RECT drect;
	int special;
} vmdef;

// left, top, right, bottom
static vmdef vmodes[11] = {
	{ 320, 240, 8, 0, 1, 1, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, 0 },  //0
	{ 320, 240, 8, 0, 1, 1, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, 0 },  //1
	{ 512, 384, 8, 0, 1, 1, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, 0 },  //2
	{ 640, 480, 8, 0, 1, 1, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, 0 },  //3
	{ 640, 480, 8, 0, 1, 1, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, 0 },  //4
	{ 640, 480, 8, 0, 1, 1, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, 0 },  //5
	{ 640, 480, 8, VMDF_DXBLT, 2, 2, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, 0 },  //6
	{ 1024, 768, 8, VMDF_DXBLT, 4, 3, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, 0 },  //7
	{ 1280, 1024, 8, VMDF_DXBLT, 5, 4, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, 0 },  //8
	{ 1600, 1200, 8, VMDF_DXBLT, 6, 5, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, 0 },  //9
	{ 800, 600, 8, VMDF_DXBLT | VMDF_STRFS, 0, 0, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, 0 }   //10
};
static DDCAPS caps;
static int mustrestore = 0;
static DWORD CBM[3];

static int bpp;
static int vflags;
static int veflags;
static int winspecial = 0;

int disvaccel = 0;      /* Disable video hardware acceleration. */

int fssync = 0;
int winsync = 0;

PALETTEENTRY color_palette[256];
static int PaletteChanged = 0;

LPDIRECTDRAWCLIPPER lpClipper = 0;
LPDIRECTDRAW lpDD = 0;
LPDIRECTDRAW7 lpDD7 = 0;
LPDIRECTDRAWPALETTE lpddpal = 0;

DDSURFACEDESC2 ddsd;

DDSURFACEDESC2 ddsdback;
LPDIRECTDRAWSURFACE7 lpDDSPrimary = 0;
LPDIRECTDRAWSURFACE7 lpDDSDBack = 0;
LPDIRECTDRAWSURFACE7 lpDDSBack = 0;

static void ShowDDErr(char *s) {
	char tempo[512];
	sprintf(tempo, "DirectDraw: %s", s);
	FCEUD_PrintError(tempo);
}

int RestoreDD(int w) {
	if (w) {
		if (!lpDDSBack) return 0;
		if (IDirectDrawSurface7_Restore(lpDDSBack) != DD_OK) return 0;
	}else   {
		if (!lpDDSPrimary) return 0;
		if (IDirectDrawSurface7_Restore(lpDDSPrimary) != DD_OK) return 0;
	}
	veflags |= 1;
	return 1;
}

void FCEUD_SetPalette(unsigned char index, unsigned char r, unsigned char g, unsigned char b) {
	color_palette[index].peRed = r;
	color_palette[index].peGreen = g;
	color_palette[index].peBlue = b;
	PaletteChanged = 1;
}

void FCEUD_GetPalette(unsigned char i, unsigned char *r, unsigned char *g, unsigned char *b) {
	*r = color_palette[i].peRed;
	*g = color_palette[i].peGreen;
	*b = color_palette[i].peBlue;
}

static int InitializeDDraw(int fs) {
	ddrval = DirectDrawCreate((disvaccel & (1 << (fs ? 1 : 0))) ? (GUID FAR*)DDCREATE_EMULATIONONLY : NULL, &lpDD, NULL);
	if (ddrval != DD_OK) {
		ShowDDErr("Error creating DirectDraw object.");
		return 0;
	}

	ddrval = IDirectDraw_QueryInterface(lpDD, &IID_IDirectDraw7, (LPVOID*)&lpDD7);
	IDirectDraw_Release(lpDD);

	if (ddrval != DD_OK) {
		ShowDDErr("Error querying interface.");
		return 0;
	}

	caps.dwSize = sizeof(caps);
	if (IDirectDraw7_GetCaps(lpDD7, &caps, 0) != DD_OK) {
		ShowDDErr("Error getting capabilities.");
		return 0;
	}
	return 1;
}

static int GetBPP(void) {
	DDPIXELFORMAT ddpix;

	memset(&ddpix, 0, sizeof(ddpix));
	ddpix.dwSize = sizeof(ddpix);

	ddrval = IDirectDrawSurface7_GetPixelFormat(lpDDSPrimary, &ddpix);
	if (ddrval != DD_OK) {
		ShowDDErr("Error getting primary surface pixel format.");
		return 0;
	}

	if (ddpix.dwFlags & DDPF_RGB) {
		bpp = ddpix.DUMMYUNIONNAMEN(1).dwRGBBitCount;
		CBM[0] = ddpix.DUMMYUNIONNAMEN(2).dwRBitMask;
		CBM[1] = ddpix.DUMMYUNIONNAMEN(3).dwGBitMask;
		CBM[2] = ddpix.DUMMYUNIONNAMEN(4).dwBBitMask;
	}else {
		ShowDDErr("RGB data not valid.");
		return 0;
	}
	if (bpp == 15) bpp = 16;

	return 1;
}

static int InitBPPStuff(int fs) {
	if (bpp >= 16) {
		InitBlitToHigh(bpp >> 3, CBM[0], CBM[1], CBM[2], 0, fs ? vmodes[vmod].special : winspecial);
	}else if (bpp == 8) {
		ddrval = IDirectDraw7_CreatePalette(lpDD7, DDPCAPS_8BIT | DDPCAPS_ALLOW256 | DDPCAPS_INITIALIZE, color_palette, &lpddpal, NULL);
		if (ddrval != DD_OK) {
			ShowDDErr("Error creating palette object.");
			return 0;
		}
		ddrval = IDirectDrawSurface7_SetPalette(lpDDSPrimary, lpddpal);
		if (ddrval != DD_OK) {
			ShowDDErr("Error setting palette object.");
			return 0;
		}
	}
	return 1;
}

int SetVideoMode(int fs) {
	int specmul = 1;        // Special scaler size multiplier


	if (fs)
		if (!vmod)
			if (!RecalcCustom())
				return(0);

	vflags = 0;
	veflags = 1;
	PaletteChanged = 1;

	ResetVideo();
	StopSound();

	if (!InitializeDDraw(fs)) return(1);        // DirectDraw not initialized


	if (!fs) {
		if (winspecial == 2 || winspecial == 1)
			specmul = 2;
		else if (winspecial == 3 || winspecial == 4)
			specmul = 3;
		else
			specmul = 1;

		ShowCursorAbs(1);
		windowedfailed = 1;
		HideFWindow(0);

		ddrval = IDirectDraw7_SetCooperativeLevel(lpDD7, hAppWnd, DDSCL_NORMAL);
		if (ddrval != DD_OK) {
			ShowDDErr("Error setting cooperative level.");
			return 1;
		}

		/* Beginning */
		memset(&ddsd, 0, sizeof(ddsd));
		ddsd.dwSize = sizeof(ddsd);
		ddsd.dwFlags = DDSD_CAPS;
		ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;

		ddrval = IDirectDraw7_CreateSurface(lpDD7, &ddsd, &lpDDSPrimary, (IUnknown FAR*)NULL);
		if (ddrval != DD_OK) {
//          FCEU_PrintError("%08x, %d\n",ddrval,lpDD7);
			ShowDDErr("Error creating primary surface.");
			return 1;
		}

		memset(&ddsdback, 0, sizeof(ddsdback));
		ddsdback.dwSize = sizeof(ddsdback);
		ddsdback.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH;
		ddsdback.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN;

		ddsdback.dwWidth = 256 * specmul;
		ddsdback.dwHeight = 240 * specmul;

		/*
		  If the blit hardware can't stretch, assume it's cheap(and slow)
		  and create the buffer in system memory.
		*/
		if (!(caps.dwCaps & DDCAPS_BLTSTRETCH))
			ddsdback.ddsCaps.dwCaps |= DDSCAPS_SYSTEMMEMORY;

		ddrval = IDirectDraw7_CreateSurface(lpDD7, &ddsdback, &lpDDSBack, (IUnknown FAR*)NULL);
		if (ddrval != DD_OK) {
			ShowDDErr("Error creating secondary surface.");
			return 0;
		}

		if (!GetBPP())
			return 0;

		if (bpp != 16 && bpp != 24 && bpp != 32) {
			ShowDDErr("Current bit depth not supported!");
			return 0;
		}

		if (!InitBPPStuff(fs))
			return 0;

		ddrval = IDirectDraw7_CreateClipper(lpDD7, 0, &lpClipper, 0);
		if (ddrval != DD_OK) {
			ShowDDErr("Error creating clipper.");
			return 0;
		}

		ddrval = IDirectDrawClipper_SetHWnd(lpClipper, 0, hAppWnd);
		if (ddrval != DD_OK) {
			ShowDDErr("Error setting clipper window.");
			return 0;
		}
		ddrval = IDirectDrawSurface7_SetClipper(lpDDSPrimary, lpClipper);
		if (ddrval != DD_OK) {
			ShowDDErr("Error attaching clipper to primary surface.");
			return 0;
		}

		windowedfailed = 0;
		SetMainWindowStuff();
	}else {    /* Following is full-screen */
		if (vmod == 0) {
			if (vmodes[0].special == 2 || vmodes[0].special == 1)
				specmul = 2;
			else if (vmodes[0].special == 3 || vmodes[0].special == 4)
				specmul = 3;
			else
				specmul = 1;
		}
		HideFWindow(1);

		ddrval = IDirectDraw7_SetCooperativeLevel(lpDD7, hAppWnd, DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN | DDSCL_ALLOWREBOOT);
		if (ddrval != DD_OK) {
			ShowDDErr("Error setting cooperative level.");
			return 0;
		}

		ddrval = IDirectDraw7_SetDisplayMode(lpDD7, vmodes[vmod].x, vmodes[vmod].y, vmodes[vmod].bpp, 0, 0);
		if (ddrval != DD_OK) {
			ShowDDErr("Error setting display mode.");
			return 0;
		}
		if (vmodes[vmod].flags & VMDF_DXBLT) {
			memset(&ddsdback, 0, sizeof(ddsdback));
			ddsdback.dwSize = sizeof(ddsdback);
			ddsdback.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH;
			ddsdback.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN;

			ddsdback.dwWidth = 256 * specmul; //vmodes[vmod].srect.right;
			ddsdback.dwHeight = 240 * specmul; //vmodes[vmod].srect.bottom;

			if (!(caps.dwCaps & DDCAPS_BLTSTRETCH))
				ddsdback.ddsCaps.dwCaps |= DDSCAPS_SYSTEMMEMORY;

			ddrval = IDirectDraw7_CreateSurface(lpDD7, &ddsdback, &lpDDSBack, (IUnknown FAR*)NULL);
			if (ddrval != DD_OK) {
				ShowDDErr("Error creating secondary surface.");
				return 0;
			}
		}

		// create foreground surface

		memset(&ddsd, 0, sizeof(ddsd));
		ddsd.dwSize = sizeof(ddsd);

		ddsd.dwFlags = DDSD_CAPS;
		ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;

		if (fssync == 3) { // Double buffering.
			ddsd.dwFlags |= DDSD_BACKBUFFERCOUNT;
			ddsd.DUMMYUNIONNAMEN(5).dwBackBufferCount = 1;
			ddsd.ddsCaps.dwCaps |= DDSCAPS_COMPLEX | DDSCAPS_FLIP;
		}

		ddrval = IDirectDraw7_CreateSurface(lpDD7, &ddsd, &lpDDSPrimary, (IUnknown FAR*)NULL);
		if (ddrval != DD_OK) {
			ShowDDErr("Error creating primary surface.");
			return 0;
		}

		if (fssync == 3) {
			DDSCAPS2 tmp;

			memset(&tmp, 0, sizeof(tmp));
			tmp.dwCaps = DDSCAPS_BACKBUFFER;

			if (IDirectDrawSurface7_GetAttachedSurface(lpDDSPrimary, &tmp, &lpDDSDBack) != DD_OK) {
				ShowDDErr("Error getting attached surface.");
				return 0;
			}
		}

		if (!GetBPP())
			return 0;
		if (!InitBPPStuff(fs))
			return 0;

		mustrestore = 1;
		ShowCursorAbs(0);
	}

	InputScreenChanged(fs);
	fullscreen = fs;
	return 1;
}

static void BlitScreenWindow(uint8 *XBuf);
static void BlitScreenFull(uint8 *XBuf);
//static uint8 *XBSave;
void FCEUD_BlitScreen(uint8 *XBuf) {
	xbsave = XBuf;
	if (!NoWaiting) {
		int ws;

		if (fullscreen) ws = fssync;
		else ws = winsync;

		if (ws == 1)
			IDirectDraw7_WaitForVerticalBlank(lpDD7, DDWAITVB_BLOCKBEGIN, 0);
		else if (ws == 2) {
			BOOL invb = 0;

			while ((DD_OK == IDirectDraw7_GetVerticalBlankStatus(lpDD7, &invb)) && !invb)
				Sleep(0);
		}
	}

	if (fullscreen) {
		BlitScreenFull(XBuf);
	}else {
		if (!windowedfailed)
			BlitScreenWindow(XBuf);
	}
}

static void FixPaletteHi(void) {
//   int x;
	SetPaletteBlitToHigh((uint8*)color_palette);
}

static void BlitScreenWindow(unsigned char *XBuf) {
	int pitch;
	unsigned char *ScreenLoc;
	static RECT srect;
	RECT drect;
	int specialmul;

	if (winspecial == 2 || winspecial == 1)
		specialmul = 2;
	else if (winspecial == 4 || winspecial == 3)
		specialmul = 3;
	else specialmul = 1;


	srect.top = srect.left = 0;
	srect.right = VNSWID * specialmul;
	srect.bottom = totallines * specialmul;

	if (PaletteChanged == 1) {
		FixPaletteHi();
		PaletteChanged = 0;
	}

	if (!GetClientAbsRect(&drect)) return;

	ddrval = IDirectDrawSurface7_Lock(lpDDSBack, NULL, &ddsdback, 0, NULL);
	if (ddrval != DD_OK) {
		if (ddrval == DDERR_SURFACELOST) RestoreDD(1);
		return;
	}

	pitch = ddsdback.DUMMYUNIONNAMEN(1).lPitch;
	ScreenLoc = ddsdback.lpSurface;
	if (veflags & 1) {
		memset(ScreenLoc, 0, pitch * 240);
		veflags &= ~1;
	}
	Blit8ToHigh(XBuf + srendline * 256 + VNSCLIP, ScreenLoc, VNSWID, totallines, pitch, specialmul, specialmul);

	IDirectDrawSurface7_Unlock(lpDDSBack, NULL);

	if (IDirectDrawSurface7_Blt(lpDDSPrimary, &drect, lpDDSBack, &srect, DDBLT_ASYNC, 0) != DD_OK) {
		ddrval = IDirectDrawSurface7_Blt(lpDDSPrimary, &drect, lpDDSBack, &srect, DDBLT_WAIT, 0);
		if (ddrval != DD_OK) {
			if (ddrval == DDERR_SURFACELOST) {
				RestoreDD(1); RestoreDD(0);
			}
			return;
		}
	}
}

static void BlitScreenFull(uint8 *XBuf) {
	static int pitch;
	char *ScreenLoc;
//  unsigned long x;
//  uint8 y;
	RECT srect, drect;
	LPDIRECTDRAWSURFACE7 lpDDSVPrimary;
	int specmul;  // Special scaler size multiplier
	if (vmodes[0].special == 2 || vmodes[0].special == 1)
		specmul = 2;
	else if (vmodes[0].special == 3 || vmodes[0].special == 4)
		specmul = 3;
	else
		specmul = 1;


	if (fssync == 3)
		lpDDSVPrimary = lpDDSDBack;
	else
		lpDDSVPrimary = lpDDSPrimary;

	if (PaletteChanged == 1) {
		if (bpp >= 16)
			FixPaletteHi();
		else{
			ddrval = IDirectDrawPalette_SetEntries(lpddpal, 0, 0, 256, color_palette);
			if (ddrval != DD_OK) {
				if (ddrval == DDERR_SURFACELOST) RestoreDD(0);
				return;
			}
		}
		PaletteChanged = 0;
	}

	if (vmodes[vmod].flags & VMDF_DXBLT) {
		ddrval = IDirectDrawSurface7_Lock(lpDDSBack, NULL, &ddsdback, 0, NULL);
		if (ddrval != DD_OK) {
			if (ddrval == DDERR_SURFACELOST) RestoreDD(1);
			return;
		}
		ScreenLoc = ddsdback.lpSurface;
		pitch = ddsdback.DUMMYUNIONNAMEN(1).lPitch;

		srect.top = 0;
		srect.left = 0;
		srect.right = VNSWID * specmul;
		srect.bottom = totallines * specmul;

		if (vmodes[vmod].flags & VMDF_STRFS) {
			drect.top = 0;
			drect.left = 0;
			drect.right = vmodes[vmod].x;
			drect.bottom = vmodes[vmod].y;
		}else {
			drect.top = (vmodes[vmod].y - (totallines * vmodes[vmod].yscale)) >> 1;
			drect.bottom = drect.top + (totallines * vmodes[vmod].yscale);
			drect.left = (vmodes[vmod].x - VNSWID * vmodes[vmod].xscale) >> 1;
			drect.right = drect.left + VNSWID * vmodes[vmod].xscale;
		}
	}else {
		ddrval = IDirectDrawSurface7_Lock(lpDDSVPrimary, NULL, &ddsd, 0, NULL);
		if (ddrval != DD_OK) {
			if (ddrval == DDERR_SURFACELOST) RestoreDD(0);
			return;
		}

		ScreenLoc = ddsd.lpSurface;
		pitch = ddsd.DUMMYUNIONNAMEN(1).lPitch;
	}

	if (veflags & 1) {
		if (vmodes[vmod].flags & VMDF_DXBLT) {
			veflags |= 2;
			memset((char*)ScreenLoc, 0, pitch * srect.bottom);
		}else {
			memset((char*)ScreenLoc, 0, pitch * vmodes[vmod].y);
		}
		PaletteChanged = 1;
		veflags &= ~1;
	}

	if (vmod == 5) {
		if (eoptions & EO_CLIPSIDES) {
			asm volatile (
				"xorl %%edx, %%edx\n\t"
				"akoop1:\n\t"
				"movb $120,%%al     \n\t"
				"akoop2:\n\t"
				"movb 1(%%esi),%%dl\n\t"
				"shl  $16,%%edx\n\t"
				"movb (%%esi),%%dl\n\t"
				"movl %%edx,(%%edi)\n\t"
				"addl $2,%%esi\n\t"
				"addl $4,%%edi\n\t"
				"decb %%al\n\t"
				"jne akoop2\n\t"
				"addl $16,%%esi\n\t"
				"addl %%ecx,%%edi\n\t"
				"decb %%bl\n\t"
				"jne akoop1\n\t"
				:
				: "S" (XBuf + srendline * 256 + VNSCLIP), "D" (ScreenLoc + ((240 - totallines) / 2) * pitch + (640 - (VNSWID << 1)) / 2), "b" (totallines), "c" ((pitch - VNSWID) << 1)
				: "%al", "%edx", "%cc");
		}else {
			asm volatile (
				"xorl %%edx, %%edx\n\t"
				"koop1:\n\t"
				"movb $128,%%al     \n\t"
				"koop2:\n\t"
				"movb 1(%%esi),%%dl\n\t"
				"shl  $16,%%edx\n\t"
				"movb (%%esi),%%dl\n\t"
				"movl %%edx,(%%edi)\n\t"
				"addl $2,%%esi\n\t"
				"addl $4,%%edi\n\t"
				"decb %%al\n\t"
				"jne koop2\n\t"
				"addl %%ecx,%%edi\n\t"
				"decb %%bl\n\t"
				"jne koop1\n\t"
				:
				: "S" (XBuf + srendline * 256), "D" (ScreenLoc + ((240 - totallines) / 2) * pitch + (640 - 512) / 2), "b" (totallines), "c" (pitch - 512 + pitch)
				: "%al", "%edx", "%cc");
		}
	}else if (vmod == 4) {
		if (eoptions & EO_CLIPSIDES) {
			asm volatile (
				"ayoop1:\n\t"
				"movb $120,%%al     \n\t"
				"ayoop2:\n\t"
				"movb 1(%%esi),%%dh\n\t"
				"movb %%dh,%%dl\n\t"
				"shl  $16,%%edx\n\t"
				"movb (%%esi),%%dl\n\t"
				"movb %%dl,%%dh\n\t"     // Ugh
				"movl %%edx,(%%edi)\n\t"
				"addl $2,%%esi\n\t"
				"addl $4,%%edi\n\t"
				"decb %%al\n\t"
				"jne ayoop2\n\t"
				"addl $16,%%esi\n\t"
				"addl %%ecx,%%edi\n\t"
				"decb %%bl\n\t"
				"jne ayoop1\n\t"
				:
				: "S" (XBuf + srendline * 256 + VNSCLIP), "D" (ScreenLoc + ((240 - totallines) / 2) * pitch + (640 - (VNSWID << 1)) / 2), "b" (totallines), "c" ((pitch - VNSWID) << 1)
				: "%al", "%edx", "%cc");
		}else {
			asm volatile (
				"yoop1:\n\t"
				"movb $128,%%al     \n\t"
				"yoop2:\n\t"
				"movb 1(%%esi),%%dh\n\t"
				"movb %%dh,%%dl\n\t"
				"shl  $16,%%edx\n\t"
				"movb (%%esi),%%dl\n\t"
				"movb %%dl,%%dh\n\t"     // Ugh
				"movl %%edx,(%%edi)\n\t"
				"addl $2,%%esi\n\t"
				"addl $4,%%edi\n\t"
				"decb %%al\n\t"
				"jne yoop2\n\t"
				"addl %%ecx,%%edi\n\t"
				"decb %%bl\n\t"
				"jne yoop1\n\t"
				:
				: "S" (XBuf + srendline * 256), "D" (ScreenLoc + ((240 - totallines) / 2) * pitch + (640 - 512) / 2), "b" (totallines), "c" (pitch - 512 + pitch)
				: "%al", "%edx", "%cc");
		}
	}else {
		if (!(vmodes[vmod].flags & VMDF_DXBLT)) {
			if (vmodes[vmod].special)
				ScreenLoc += (vmodes[vmod].drect.left * (bpp >> 3)) + ((vmodes[vmod].drect.top) * pitch);
			else
				ScreenLoc += ((vmodes[vmod].x - VNSWID) >> 1) * (bpp >> 3) + (((vmodes[vmod].y - totallines) >> 1)) * pitch;
		}

		if (bpp >= 16) {
			Blit8ToHigh(XBuf + srendline * 256 + VNSCLIP, ScreenLoc, VNSWID, totallines, pitch, specmul, specmul);
		}else {
			XBuf += srendline * 256 + VNSCLIP;
			if (vmodes[vmod].special)
				Blit8To8(XBuf, ScreenLoc, VNSWID, totallines, pitch, vmodes[vmod].xscale, vmodes[vmod].yscale, 0, vmodes[vmod].special);
			else
				Blit8To8(XBuf, ScreenLoc, VNSWID, totallines, pitch, 1, 1, 0, 0);
		}
	}

	if (vmodes[vmod].flags & VMDF_DXBLT) {
		IDirectDrawSurface7_Unlock(lpDDSBack, NULL);

		if (veflags & 2) {
			if (IDirectDrawSurface7_Lock(lpDDSVPrimary, NULL, &ddsd, 0, NULL) == DD_OK) {
				memset(ddsd.lpSurface, 0, ddsd.DUMMYUNIONNAMEN(1).lPitch * vmodes[vmod].y);
				IDirectDrawSurface7_Unlock(lpDDSVPrimary, NULL);
				veflags &= ~2;
			}
		}


		if (IDirectDrawSurface7_Blt(lpDDSVPrimary, &drect, lpDDSBack, &srect, DDBLT_ASYNC, 0) != DD_OK) {
			ddrval = IDirectDrawSurface7_Blt(lpDDSVPrimary, &drect, lpDDSBack, &srect, DDBLT_WAIT, 0);
			if (ddrval != DD_OK) {
				if (ddrval == DDERR_SURFACELOST) {
					RestoreDD(0);
					RestoreDD(1);
				}
				return;
			}
		}
	}else
		IDirectDrawSurface7_Unlock(lpDDSVPrimary, NULL);
	if (fssync == 3) {
		IDirectDrawSurface7_Flip(lpDDSPrimary, 0, 0);
	}
}

void ResetVideo(void) {
	ShowCursorAbs(1);
	KillBlitToHigh();
	if (lpDD7)
		if (mustrestore) {
			IDirectDraw7_RestoreDisplayMode(lpDD7); mustrestore = 0;
		}
	if (lpddpal) {
		IDirectDrawPalette_Release(lpddpal); lpddpal = 0;
	}
	if (lpDDSBack) {
		IDirectDrawSurface7_Release(lpDDSBack); lpDDSBack = 0;
	}
	if (lpDDSPrimary) {
		IDirectDrawSurface7_Release(lpDDSPrimary); lpDDSPrimary = 0;
	}
	if (lpClipper) {
		IDirectDrawClipper_Release(lpClipper); lpClipper = 0;
	}
	if (lpDD7) {
		IDirectDraw_Release(lpDD7); lpDD7 = NULL;
	}
}

int specialmlut[5] = { 1, 2, 2, 3, 3 };

static int RecalcCustom(void) {
	vmdef *cmode = &vmodes[0];

	cmode->flags &= ~VMDF_DXBLT;

	if (cmode->flags & VMDF_STRFS) {
		cmode->flags |= VMDF_DXBLT;
	}else if (cmode->xscale != 1 || cmode->yscale != 1 || cmode->special) {
		if (cmode->special) {
			int mult = specialmlut[cmode->special];

			if (cmode->xscale < mult)
				cmode->xscale = mult;
			if (cmode->yscale < mult)
				cmode->yscale = mult;

			if (cmode->xscale != mult || cmode->yscale != mult)
				cmode->flags |= VMDF_DXBLT;
		}else
			cmode->flags |= VMDF_DXBLT;


		if (VNSWID * cmode->xscale > cmode->x) {
			if (cmode->special) {
				FCEUD_PrintError("Scaled width is out of range.");
				return(0);
			}else {
				FCEUD_PrintError("Scaled width is out of range.  Reverting to no horizontal scaling.");
				cmode->xscale = 1;
			}
		}
		if (totallines * cmode->yscale > cmode->y) {
			if (cmode->special) {
				FCEUD_PrintError("Scaled height is out of range.");
				return(0);
			}else {
				FCEUD_PrintError("Scaled height is out of range.  Reverting to no vertical scaling.");
				cmode->yscale = 1;
			}
		}

		cmode->srect.left = VNSCLIP;
		cmode->srect.top = srendline;
		cmode->srect.right = 256 - VNSCLIP;
		cmode->srect.bottom = erendline + 1;

		cmode->drect.top = (cmode->y - (totallines * cmode->yscale)) >> 1;
		cmode->drect.bottom = cmode->drect.top + totallines * cmode->yscale;

		cmode->drect.left = (cmode->x - (VNSWID * cmode->xscale)) >> 1;
		cmode->drect.right = cmode->drect.left + VNSWID * cmode->xscale;
	}

	if ((cmode->special == 1 || cmode->special == 3) && cmode->bpp == 8) {
		cmode->bpp = 32;
		//FCEUD_PrintError("HQ2x/HQ3x requires 16bpp or 32bpp(best).");
		//return(0);
	}

	if (cmode->x < VNSWID) {
		FCEUD_PrintError("Horizontal resolution is too low.");
		return(0);
	}
	if (cmode->y < totallines && !(cmode->flags & VMDF_STRFS)) {
		FCEUD_PrintError("Vertical resolution must not be less than the total number of drawn scanlines.");
		return(0);
	}

	return(1);
}

BOOL SetDlgItemDouble(HWND hDlg, int item, double value) {
	char buf[8];
	sprintf(buf, "%.6f", value);
	SetDlgItemText(hDlg, item, buf);
	return TRUE;
}

double GetDlgItemDouble(HWND hDlg, int item) {
	char buf[8];
	double ret = 0;

	GetDlgItemText(hDlg, item, buf, 8);
	sscanf(buf, "%lf", &ret);
	return(ret);
}

BOOL CALLBACK VideoConCallB(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
	static char *vmstr[11] = {
		"Custom",
		"320x240 Full Screen",
		"512x384 Centered",
		"640x480 Centered",
		"640x480 Scanlines",
		"640x480 \"4 per 1\"",
		"640x480 2x,2y",
		"1024x768 4x,3y",
		"1280x1024 5x,4y",
		"1600x1200 6x,5y",
		"800x600 Stretched"
	};
	int x;

	switch (uMsg) {
	case WM_INITDIALOG:
		for (x = 0; x < 11; x++)
			SendDlgItemMessage(hwndDlg, 100, CB_ADDSTRING, 0, (LPARAM)(LPSTR)vmstr[x]);
		SendDlgItemMessage(hwndDlg, 100, CB_SETCURSEL, vmod, (LPARAM)(LPSTR)0);

		SendDlgItemMessage(hwndDlg, 202, CB_ADDSTRING, 0, (LPARAM)(LPSTR)"8");
		SendDlgItemMessage(hwndDlg, 202, CB_ADDSTRING, 0, (LPARAM)(LPSTR)"16");
		SendDlgItemMessage(hwndDlg, 202, CB_ADDSTRING, 0, (LPARAM)(LPSTR)"24");
		SendDlgItemMessage(hwndDlg, 202, CB_ADDSTRING, 0, (LPARAM)(LPSTR)"32");
		SendDlgItemMessage(hwndDlg, 202, CB_SETCURSEL, (vmodes[0].bpp >> 3) - 1, (LPARAM)(LPSTR)0);

		SetDlgItemInt(hwndDlg, 200, vmodes[0].x, 0);
		SetDlgItemInt(hwndDlg, 201, vmodes[0].y, 0);

		SetDlgItemInt(hwndDlg, 302, vmodes[0].xscale, 0);
		SetDlgItemInt(hwndDlg, 303, vmodes[0].yscale, 0);
		CheckRadioButton(hwndDlg, 300, 301, (vmodes[0].flags & VMDF_STRFS) ? 301 : 300);

		{
			char *str[] = { "<none>", "hq2x", "Scale2x", "hq3x", "Scale3x" };
			int x;
			for (x = 0; x < 5; x++) {
				SendDlgItemMessage(hwndDlg, 304, CB_ADDSTRING, 0, (LPARAM)(LPSTR)str[x]);
				SendDlgItemMessage(hwndDlg, 406, CB_ADDSTRING, 0, (LPARAM)(LPSTR)str[x]);
			}
		}
		SendDlgItemMessage(hwndDlg, 304, CB_SETCURSEL, vmodes[0].special, (LPARAM)(LPSTR)0);
		SendDlgItemMessage(hwndDlg, 406, CB_SETCURSEL, winspecial, (LPARAM)(LPSTR)0);

		if (eoptions & EO_FSAFTERLOAD)
			CheckDlgButton(hwndDlg, 102, BST_CHECKED);

		if (eoptions & EO_CLIPSIDES)
			CheckDlgButton(hwndDlg, 106, BST_CHECKED);

		if (disvaccel & 1)
			CheckDlgButton(hwndDlg, 130, BST_CHECKED);

		if (disvaccel & 2)
			CheckDlgButton(hwndDlg, 131, BST_CHECKED);

		if (eoptions & EO_FORCEISCALE)
			CheckDlgButton(hwndDlg, 402, BST_CHECKED);

		if (eoptions & EO_FORCEASPECT)
			CheckDlgButton(hwndDlg, 403, BST_CHECKED);

		SetDlgItemInt(hwndDlg, 500, srendlinen, 0);
		SetDlgItemInt(hwndDlg, 501, erendlinen, 0);

		SetDlgItemInt(hwndDlg, 502, srendlinep, 0);
		SetDlgItemInt(hwndDlg, 503, erendlinep, 0);


		SetDlgItemDouble(hwndDlg, 400, winsizemulx);
		SetDlgItemDouble(hwndDlg, 401, winsizemuly);
		SetDlgItemDouble(hwndDlg, 404, saspectw);
		SetDlgItemDouble(hwndDlg, 405, saspecth);

		//SetDlgI               temInt(hwndDlg,103,winsizemul,0);

		SendDlgItemMessage(hwndDlg, 104, CB_ADDSTRING, 0, (LPARAM)(LPSTR)"<none>");
		SendDlgItemMessage(hwndDlg, 105, CB_ADDSTRING, 0, (LPARAM)(LPSTR)"<none>");

		SendDlgItemMessage(hwndDlg, 104, CB_ADDSTRING, 0, (LPARAM)(LPSTR)"Wait for VBlank");
		SendDlgItemMessage(hwndDlg, 104, CB_ADDSTRING, 0, (LPARAM)(LPSTR)"Lazy wait for VBlank");

		SendDlgItemMessage(hwndDlg, 105, CB_ADDSTRING, 0, (LPARAM)(LPSTR)"Wait for VBlank");
		SendDlgItemMessage(hwndDlg, 105, CB_ADDSTRING, 0, (LPARAM)(LPSTR)"Lazy wait for VBlank");
		SendDlgItemMessage(hwndDlg, 105, CB_ADDSTRING, 0, (LPARAM)(LPSTR)"Double Buffering");

		SendDlgItemMessage(hwndDlg, 104, CB_SETCURSEL, winsync, (LPARAM)(LPSTR)0);
		SendDlgItemMessage(hwndDlg, 105, CB_SETCURSEL, fssync, (LPARAM)(LPSTR)0);

		if (eoptions & EO_NOSPRLIM)
			CheckDlgButton(hwndDlg, 600, BST_CHECKED);

		break;
	case WM_CLOSE:
	case WM_QUIT: goto gornk;
	case WM_COMMAND:
		if (!(wParam >> 16))
			switch (wParam & 0xFFFF) {
			case 1:
 gornk:

				if (IsDlgButtonChecked(hwndDlg, 106) == BST_CHECKED)
					eoptions |= EO_CLIPSIDES;
				else
					eoptions &= ~EO_CLIPSIDES;

				if (IsDlgButtonChecked(hwndDlg, 600) == BST_CHECKED)
					eoptions |= EO_NOSPRLIM;
				else
					eoptions &= ~EO_NOSPRLIM;

				srendlinen = GetDlgItemInt(hwndDlg, 500, 0, 0);
				erendlinen = GetDlgItemInt(hwndDlg, 501, 0, 0);
				srendlinep = GetDlgItemInt(hwndDlg, 502, 0, 0);
				erendlinep = GetDlgItemInt(hwndDlg, 503, 0, 0);


				if (erendlinen > 239) erendlinen = 239;
				if (srendlinen > erendlinen) srendlinen = erendlinen;

				if (erendlinep > 239) erendlinep = 239;
				if (srendlinep > erendlinen) srendlinep = erendlinep;

				UpdateRendBounds();

				if (IsDlgButtonChecked(hwndDlg, 301) == BST_CHECKED)
					vmodes[0].flags |= VMDF_STRFS;
				else
					vmodes[0].flags &= ~VMDF_STRFS;

				vmod = SendDlgItemMessage(hwndDlg, 100, CB_GETCURSEL, 0, (LPARAM)(LPSTR)0);
				vmodes[0].x = GetDlgItemInt(hwndDlg, 200, 0, 0);
				vmodes[0].y = GetDlgItemInt(hwndDlg, 201, 0, 0);
				vmodes[0].bpp = (SendDlgItemMessage(hwndDlg, 202, CB_GETCURSEL, 0, (LPARAM)(LPSTR)0) + 1) << 3;

				vmodes[0].xscale = GetDlgItemInt(hwndDlg, 302, 0, 0);
				vmodes[0].yscale = GetDlgItemInt(hwndDlg, 303, 0, 0);
				vmodes[0].special = SendDlgItemMessage(hwndDlg, 304, CB_GETCURSEL, 0, (LPARAM)(LPSTR)0);

				winspecial = SendDlgItemMessage(hwndDlg, 406, CB_GETCURSEL, 0, (LPARAM)(LPSTR)0);
				disvaccel = 0;

				if (IsDlgButtonChecked(hwndDlg, 130) == BST_CHECKED)
					disvaccel |= 1;
				if (IsDlgButtonChecked(hwndDlg, 131) == BST_CHECKED)
					disvaccel |= 2;


				if (IsDlgButtonChecked(hwndDlg, 101) == BST_CHECKED)
					fullscreen = 1;
				else
					fullscreen = 0;
				if (IsDlgButtonChecked(hwndDlg, 102) == BST_CHECKED)
					eoptions |= EO_FSAFTERLOAD;
				else
					eoptions &= ~EO_FSAFTERLOAD;

				eoptions &= ~(EO_FORCEISCALE | EO_FORCEASPECT);
				if (IsDlgButtonChecked(hwndDlg, 402) == BST_CHECKED)
					eoptions |= EO_FORCEISCALE;
				if (IsDlgButtonChecked(hwndDlg, 403) == BST_CHECKED)
					eoptions |= EO_FORCEASPECT;


				winsizemulx = GetDlgItemDouble(hwndDlg, 400);
				winsizemuly = GetDlgItemDouble(hwndDlg, 401);
				saspectw = GetDlgItemDouble(hwndDlg, 404);
				saspecth = GetDlgItemDouble(hwndDlg, 405);
				FixWXY(0);

				winsync = SendDlgItemMessage(hwndDlg, 104, CB_GETCURSEL, 0, (LPARAM)(LPSTR)0);
				fssync = SendDlgItemMessage(hwndDlg, 105, CB_GETCURSEL, 0, (LPARAM)(LPSTR)0);
				EndDialog(hwndDlg, 0);
				break;
			}
	}
	return 0;
}

static void SetFSVideoMode(void) {
	changerecursive = 1;
	if (!SetVideoMode(1))
		SetVideoMode(0);
	changerecursive = 0;
}


void DoVideoConfigFix(void) {
	FCEUI_DisableSpriteLimitation(eoptions & EO_NOSPRLIM);
	UpdateRendBounds();
}

void ConfigVideo(void) {
	DialogBox(fceu_hInstance, "VIDEOCONFIG", hAppWnd, VideoConCallB);
	DoVideoConfigFix();
	if (fullscreen)
		SetFSVideoMode();
	else{
		changerecursive = 1;
		SetVideoMode(0);
		changerecursive = 0;
	}
	//SetMainWindowStuff();
}

